/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.swak.registry.retry;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.swak.Constants;
import com.swak.registry.FailbackRegistry;
import com.swak.registry.URL;
import com.swak.timer.Timeout;
import com.swak.timer.Timer;
import com.swak.timer.TimerTask;
import com.swak.utils.StringUtils;

/**
 * AbstractRetryTask
 */
public abstract class AbstractRetryTask implements TimerTask {

	protected final Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * url for retry task
	 */
	protected final URL url;

	/**
	 * registry for this task
	 */
	protected final FailbackRegistry registry;

	/**
	 * retry period
	 */
	final long retryPeriod;

	/**
	 * define the most retry times
	 */
	private final int retryTimes;

	/**
	 * task name for this task
	 */
	private final String taskName;

	/**
	 * times of retry. retry task is execute in single thread so that the times is
	 * not need volatile.
	 */
	private int times = 1;

	private volatile boolean cancel;

	AbstractRetryTask(URL url, FailbackRegistry registry, String taskName) {
		if (url == null || StringUtils.isBlank(taskName)) {
			throw new IllegalArgumentException();
		}
		this.url = url;
		this.registry = registry;
		this.taskName = taskName;
		cancel = false;
		this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY,
				Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
		this.retryTimes = url.getParameter(Constants.REGISTRY_RETRY_TIMES_KEY, Constants.DEFAULT_REGISTRY_RETRY_TIMES);
	}

	public void cancel() {
		cancel = true;
	}

	public boolean isCancel() {
		return cancel;
	}

	protected void reput(Timeout timeout, long tick) {
		if (timeout == null) {
			throw new IllegalArgumentException();
		}

		Timer timer = timeout.timer();
		if (timer.isStop() || timeout.isCancelled() || isCancel()) {
			return;
		}
		times++;
		timer.newTimeout(timeout.task(), tick, TimeUnit.MILLISECONDS);
	}

	@Override
	public void run(Timeout timeout) throws Exception {
		if (timeout.isCancelled() || timeout.timer().isStop() || isCancel()) {
			// other thread cancel this timeout or stop the timer.
			return;
		}
		if (times > retryTimes) {
			// reach the most times of retry.
			logger.warn(
					"Final failed to execute task " + taskName + ", url: " + url + ", retry " + retryTimes + " times.");
			return;
		}
		if (logger.isInfoEnabled()) {
			logger.info(taskName + " : " + url);
		}
		try {
			doRetry(url, registry, timeout);
		} catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
			logger.warn("Failed to execute task " + taskName + ", url: " + url + ", waiting for again, cause:"
					+ t.getMessage(), t);
			// reput this task when catch exception.
			reput(timeout, retryPeriod);
		}
	}

	protected abstract void doRetry(URL url, FailbackRegistry registry, Timeout timeout);
}
